package cn.flying.cloud.oauth.server.configuration.authorization;

import javax.annotation.Resource;
import javax.sql.DataSource;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import cn.flying.cloud.oauth.server.configuration.details.CustomClientDetailsService;
import cn.flying.cloud.oauth.server.configuration.details.CustomUserDetailService;
import cn.flying.cloud.oauth.server.configuration.filter.CustomClientCredentialsTokenEndpointFilter;
import cn.flying.cloud.oauth.server.configuration.granter.SmsCodeTokenGranter;
import cn.flying.cloud.oauth.server.configuration.handler.CustomOAuth2AuthenticationEntryPointHandler;

/**
 * 认证授权服务配置
 * 是整个认证服务实现的核心。总结下来就是两个关键点，客户端信息配置和access_token生成配置
 *
 * @author: admin
 * @date: 2023年06月19日 14:55
 * @version: 1.0
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 注入认证控制器 来支持 password grant type
     */
    @Resource
    private AuthenticationManager authenticationManager;
    /**
     * 注入userDetailsService，开启refresh_token需要用到
     */
    @Resource
    private CustomUserDetailService customUserDetailService;
    @Resource
    private DataSource dataSource;
    @Resource
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    @Resource
    private AuthorizationServerTokenServices authorizationServerTokenServices;
    @Resource
    private CustomClientDetailsService customClientDetailsService;

    /**
     * 令牌访问端点配安全策略置
     * 注意：如果需要将oauth认证的异常信息转换为Rt对象返回，则不能开启allowFormAuthenticationForClients，同时需要自定义处理拦截器
     * 如果allowFormAuthenticationForClients开启，则后续配置的authenticationEntryPoint和accessDeniedHandler无效
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 配置自定义处理客户端认证异常过滤器信息
        CustomClientCredentialsTokenEndpointFilter filter = new CustomClientCredentialsTokenEndpointFilter(security);
        filter.afterPropertiesSet();
        filter.setAuthenticationEntryPoint(new CustomOAuth2AuthenticationEntryPointHandler());

        security.addTokenEndpointAuthenticationFilter(filter);

        //解决访问/oauth/check_token 403的问题
        //checkTokenAccess是指一个 Token 校验的端点，这个端点我们设置为可以直接访问（在后面，当资源服务器收到 Token 之后，需要去校验 Token 的合法性，就会访问这个端点）
        security.checkTokenAccess("permitAll()")
                //获取密钥公钥， /oauth/token_key
                .tokenKeyAccess("permitAll()")
        //允许client_id的表单认证
//                .allowFormAuthenticationForClients()
        // 客户端认证异常处理
//                .authenticationEntryPoint(new CustomOAuth2AuthenticationEntryPoint())
//                .accessDeniedHandler(new CustomOAuth2AccessDeniedHandler())
        ;
    }

    /**
     * OAuth2客户端信息配置
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //从数据库中读取加载客户端配置信息
        clients.withClientDetails(customClientDetailsService);
    }

    /**
     * 配置授权（authorization）以及令牌（token）的访问端点和令牌服务(token services)
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //密码模式需要的AuthenticationManager
        endpoints.authenticationManager(authenticationManager);
        //授权码模式需要的AuthorizationCodeServices
        endpoints.authorizationCodeServices(authorizationCodeServices(dataSource));
        //配置查询用户信息的userDetailsService
        endpoints.userDetailsService(customUserDetailService);
        //令牌管理服务需要的AuthorizationServerTokenServices，配置token存储方式
        endpoints.tokenServices(authorizationServerTokenServices);
        endpoints.accessTokenConverter(jwtAccessTokenConverter);
        //配置/oauth/token申请令牌的uri只允许POST提交
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.POST);
        //自定义登录或者鉴权失败时的返回信息
//        endpoints.exceptionTranslator(new CustomWebResponseExceptionTranslator());
        endpoints.exceptionTranslator(new DefaultWebResponseExceptionTranslator());
        // refresh_token有两种使用方式：重复使用(true)、非重复使用(false)，默认为true
        //      1.重复使用：access_token过期刷新时， refresh token过期时间未改变，仍以初次生成的时间为准
        //      2.非重复使用：access_token过期刷新时， refresh_token过期时间延续，在refresh_token有效期内刷新而无需失效再次登录
        endpoints.reuseRefreshTokens(false);
        // 授权页面路径映射，默认是/oauth/confirm_access，如果需要修改为自定义，映射即可
        endpoints.pathMapping("/oauth/confirm_access", "/oauth/approval");

        List<TokenGranter> tokenGranters = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));

        // 验证码方式
        tokenGranters.add(new SmsCodeTokenGranter(authorizationServerTokenServices, customClientDetailsService,
                endpoints.getOAuth2RequestFactory(), authenticationManager));

        endpoints.tokenGranter(new CompositeTokenGranter(tokenGranters));
    }

    /**
     * client基于数据库认证，会去查询数据库oauth_client_details
     *
     * @return
     */
//    public ClientDetailsService clientDetailsService() {
//        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
//        clientDetailsService.setPasswordEncoder(passwordEncoder);
//        return clientDetailsService;
//    }

    /**
     * 授权码存储方式
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
        return new JdbcAuthorizationCodeServices(dataSource);
    }
}
